#pragma once

#include <AVUIPoint.h>

namespace AVUI {

#pragma region Internal Matrix Type Enum

class MatrixTypes
{
public:
    enum Enum
    {
        TRANSFORM_IS_IDENTITY = 0,
        TRANSFORM_IS_TRANSLATION = 1,
        TRANSFORM_IS_SCALING = 2,
        TRANSFORM_IS_UNKNOWN = 4
    };
};

#pragma endregion Internal Matrix Type Enum


/// <summary> Represents a 3x3 affine transformation matrix used for transformations in 2-D space. </summary>
class Matrix
{
    friend class MatrixUtil;
    friend class TranslateTransform;
public:

#pragma region Public Methods

    Matrix() : m_m11(0.0f), m_m12(0.0f), m_m21(0.0f), m_m22(0.0f), m_offsetX(0.0f), m_offsetY(0.0f), m_type(MatrixTypes::TRANSFORM_IS_IDENTITY) { }

    /// <summary> Initializes a new instance of the <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <param name="m11">The new <see cref="T:System.Windows.Media.Matrix" /> structure's <see cref="P:System.Windows.Media.Matrix.M11" /> coefficient.</param>
    /// <param name="m12">The new <see cref="T:System.Windows.Media.Matrix" /> structure's <see cref="P:System.Windows.Media.Matrix.M12" /> coefficient.</param>
    /// <param name="m21">The new <see cref="T:System.Windows.Media.Matrix" /> structure's <see cref="P:System.Windows.Media.Matrix.M21" /> coefficient.</param>
    /// <param name="m22">The new <see cref="T:System.Windows.Media.Matrix" /> structure's <see cref="P:System.Windows.Media.Matrix.M22" /> coefficient.</param>
    /// <param name="offsetX">The new <see cref="T:System.Windows.Media.Matrix" /> structure's <see cref="P:System.Windows.Media.Matrix.OffsetX" /> coefficient.</param>
    /// <param name="offsetY">The new <see cref="T:System.Windows.Media.Matrix" /> structure's <see cref="P:System.Windows.Media.Matrix.OffsetY" /> coefficient.</param>
    Matrix(float m11, float m12, float m21, float m22, float offsetX, float offsetY) : m_m11(m11), m_m12(m12), m_m21(m21), m_m22(m22), 
                                                                                       m_offsetX(offsetX), m_offsetY(offsetY), m_type(MatrixTypes::TRANSFORM_IS_UNKNOWN)
    {
        DeriveMatrixType();
    }

    /// <summary> Appends the specified <see cref="T:System.Windows.Media.Matrix" /> structure to this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <param name="matrix">The <see cref="T:System.Windows.Media.Matrix" /> structure to append to this <see cref="T:System.Windows.Media.Matrix" /> structure.</param>
    void Append(const Matrix& matrix) { *this = *this * matrix; }

    /// <summary> Inverts this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <exception cref="T:System.InvalidOperationException">The <see cref="T:System.Windows.Media.Matrix" /> structure is not invertible.</exception>
    void Invert();

    /// <summary> Multiplies a <see cref="T:System.Windows.Media.Matrix" /> structure by another <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns>The result of multiplying <paramref name="trans1" /> by <paramref name="trans2" />.</returns>
    /// <param name="trans1">The first <see cref="T:System.Windows.Media.Matrix" /> structure to multiply.</param>
    /// <param name="trans2">The second <see cref="T:System.Windows.Media.Matrix" /> structure to multiply.</param>
    static Matrix Multiply(Matrix lhs, const Matrix& rhs);

    /// <summary> Determines whether the two specified <see cref="T:System.Windows.Media.Matrix" /> structures are identical.</summary>
    /// <returns>true if <paramref name="matrix1" /> and <paramref name="matrix2" /> are identical; otherwise, false.</returns>
    /// <param name="matrix1">The first <see cref="T:System.Windows.Media.Matrix" /> structure to compare.</param>
    /// <param name="matrix2">The second <see cref="T:System.Windows.Media.Matrix" /> structure to compare.</param>
    bool operator==(const Matrix& rhs) const;

    /// <summary> Determines whether the two specified <see cref="T:System.Windows.Media.Matrix" /> structures are not identical.</summary>
    /// <returns>true if <paramref name="matrix1" /> and <paramref name="matrix2" /> are not identical; otherwise, false.</returns>
    /// <param name="matrix1">The first <see cref="T:System.Windows.Media.Matrix" /> structure to compare.</param>
    /// <param name="matrix2">The second <see cref="T:System.Windows.Media.Matrix" /> structure to compare.</param>
    bool operator!=(const Matrix& rhs) const { return !(*this == rhs); }

    friend Matrix operator*(const Matrix& lhs, const Matrix& rhs);

    /// <summary> Prepends the specified <see cref="T:System.Windows.Media.Matrix" /> structure onto this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns />
    /// <param name="matrix">The <see cref="T:System.Windows.Media.Matrix" /> structure to prepend to this <see cref="T:System.Windows.Media.Matrix" /> structure.</param>
    void Prepend(const Matrix& matrix);

    /// <summary> Applies a rotation of the specified angle about the origin of this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <param name="angle">The angle of rotation.</param>
    void Rotate(float angle);

    /// <summary>Rotates this matrix about the specified point.</summary>
    /// <param name="angle">The angle, in degrees, by which to rotate this matrix. </param>
    /// <param name="centerX">The x-coordinate of the point about which to rotate this matrix.</param>
    /// <param name="centerY">The y-coordinate of the point about which to rotate this matrix.</param>
    void RotateAt(float angle, float centerX, float centerY);

    /// <summary>Prepends a rotation of the specified angle at the specified point to this <see cref="T:System.Windows.Media.Matrix" /> structure.</summary>
    /// <param name="angle">The rotation angle, in degrees.</param>
    /// <param name="centerX">The x-coordinate of the rotation center.</param>
    /// <param name="centerY">The y-coordinate of the rotation center.</param>
    void RotateAtPrepend(float angle, float centerX, float centerY);

    /// <summary> Prepends a rotation of the specified angle to this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns />
    /// <param name="angle">The angle of rotation to prepend.</param>
    void RotatePrepend(float angle);

    /// <summary> Appends the specified scale vector to this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns />
    /// <param name="scaleX">The value by which to scale this <see cref="T:System.Windows.Media.Matrix" /> along the x-axis.</param>
    /// <param name="scaleY">The value by which to scale this <see cref="T:System.Windows.Media.Matrix" /> along the y-axis.</param>
    void Scale(float scaleX, float scaleY);

    /// <summary>Scales this <see cref="T:System.Windows.Media.Matrix" /> by the specified amount about the specified point.</summary>
    /// <param name="scaleX">The amount by which to scale this <see cref="T:System.Windows.Media.Matrix" /> along the x-axis. </param>
    /// <param name="scaleY">The amount by which to scale this <see cref="T:System.Windows.Media.Matrix" /> along the y-axis.</param>
    /// <param name="centerX">The x-coordinate of the scale operation's center point.</param>
    /// <param name="centerY">The y-coordinate of the scale operation's center point.</param>
    void ScaleAt(float scaleX, float scaleY, float centerX, float centerY);

    /// <summary>Prepends the specified scale about the specified point of this <see cref="T:System.Windows.Media.Matrix" />.</summary>
    /// <param name="scaleX">The x-axis scale factor.</param>
    /// <param name="scaleY">The y-axis scale factor.</param>
    /// <param name="centerX">The x-coordinate of the point about which the scale operation is performed.</param>
    /// <param name="centerY">The y-coordinate of the point about which the scale operation is performed.</param>
    void ScaleAtPrepend(float scaleX, float scaleY, float centerX, float centerY);

    /// <summary> Prepends the specified scale vector to this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns />
    /// <param name="scaleX">The value by which to scale this <see cref="T:System.Windows.Media.Matrix" /> structure along the x-axis.</param>
    /// <param name="scaleY">The value by which to scale this <see cref="T:System.Windows.Media.Matrix" /> structure along the y-axis.</param>
    void ScalePrepend(float scaleX, float scaleY);

    /// <summary> Changes this <see cref="T:System.Windows.Media.Matrix" /> structure into an identity matrix. </summary>
    /// <returns />
    void SetIdentity();

    /// <summary> Appends a skew of the specified degrees in the x and y dimensions to this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns />
    /// <param name="skewX">The angle in the x dimension by which to skew this <see cref="T:System.Windows.Media.Matrix" />.</param>
    /// <param name="skewY">The angle in the y dimension by which to skew this <see cref="T:System.Windows.Media.Matrix" />.</param>
    void Skew(float skewX, float skewY);

    /// <summary> Prepends a skew of the specified degrees in the x and y dimensions to this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns />
    /// <param name="skewX">The angle in the x dimension by which to skew this <see cref="T:System.Windows.Media.Matrix" />.</param>
    /// <param name="skewY">The angle in the y dimension by which to skew this <see cref="T:System.Windows.Media.Matrix" />.</param>
    void SkewPrepend(float skewX, float skewY);

    /// <summary>Transforms the specified point by the <see cref="T:System.Windows.Media.Matrix" /> and returns the result.</summary>
    /// <returns>The result of transforming <paramref name="point" /> by this <see cref="T:System.Windows.Media.Matrix" />.</returns>
    /// <param name="point">The point to transform.</param>
    Point Transform(const Point& point) const;

    /// <summary>Transforms the specified points by this <see cref="T:System.Windows.Media.Matrix" />. </summary>
    /// <param name="rgPoints">The points to transform. The original points in the array are replaced by their transformed values.</param>
    /// <param name="cPoints">The number of points to transform.</param>
    void Transform(Point* rgPoints, int cPoints) const;

    /// <summary>Transforms the specified vectors by this <see cref="T:System.Windows.Media.Matrix" />.</summary>
    /// <param name="rgVectors">The vectors to transform. The original vectors in the array are replaced by their transformed values.</param>
    /// <param name="cVectors">The number of vectors to transform.</param>
    void Transform(Vector* rgVectors, int cVectors) const;

    /// <summary>Transforms the specified vector by this <see cref="T:System.Windows.Media.Matrix" />.</summary>
    /// <returns>The result of transforming <paramref name="vector" /> by this <see cref="T:System.Windows.Media.Matrix" />.</returns>
    /// <param name="vector">The vector to transform.</param>
    Vector Transform(const Vector& vector) const;

    /// <summary> Appends a translation of the specified offsets to this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns />
    /// <param name="offsetX">The amount to offset this <see cref="T:System.Windows.Media.Matrix" /> along the x-axis.</param>
    /// <param name="offsetY">The amount to offset this <see cref="T:System.Windows.Media.Matrix" /> along the y-axis.</param>
    void Translate(float offsetX, float offsetY);

    /// <summary> Prepends a translation of the specified offsets to this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns />
    /// <param name="offsetX">The amount to offset this <see cref="T:System.Windows.Media.Matrix" /> along the x-axis.</param>
    /// <param name="offsetY">The amount to offset this <see cref="T:System.Windows.Media.Matrix" /> along the y-axis.</param>
    void TranslatePrepend(float offsetX, float offsetY);

#pragma endregion Public Methods

#pragma region Public Properties

    /// <summary> Gets the determinant of this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns>The determinant of this <see cref="T:System.Windows.Media.Matrix" />.</returns>
    float get_Determinant() const;

    /// <summary> Gets a value that indicates whether this <see cref="T:System.Windows.Media.Matrix" /> structure is invertible. </summary>
    /// <returns>true if the <see cref="T:System.Windows.Media.Matrix" /> has an inverse; otherwise, false. The default is true.</returns>
    bool get_HasInverse() const;

    /// <summary> Gets an identity <see cref="T:System.Windows.Media.Matrix" />. </summary>
    /// <returns>An identity matrix.</returns>
    static Matrix get_Identity();

    /// <summary> Gets a value that indicates whether this <see cref="T:System.Windows.Media.Matrix" /> structure is an identity matrix. </summary>
    /// <returns>true if the <see cref="T:System.Windows.Media.Matrix" /> structure is an identity matrix; otherwise, false. The default is true.</returns>
    bool get_IsIdentity() const;

    /// <summary>Gets or sets the value of the first row and first column of this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns>The value of the first row and first column of this <see cref="T:System.Windows.Media.Matrix" />. The default value is 1.</returns>
    float get_M11() const;
    void set_M11(float m11);

    /// <summary> Gets or sets the value of the first row and second column of this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns>The value of the first row and second column of this <see cref="T:System.Windows.Media.Matrix" />. The default value is 0.</returns>
    float get_M12() const;
    void set_M12(float m11);

    /// <summary> Gets or sets the value of the second row and first column of this <see cref="T:System.Windows.Media.Matrix" /> structure.</summary>
    /// <returns>The value of the second row and first column of this <see cref="T:System.Windows.Media.Matrix" />. The default value is 0.</returns>
    float get_M21() const;
    void set_M21(float m11);

    /// <summary> Gets or sets the value of the second row and first column of this <see cref="T:System.Windows.Media.Matrix" /> structure.</summary>
    /// <returns>The value of the second row and first column of this <see cref="T:System.Windows.Media.Matrix" />. The default value is 0.</returns>
    float get_M22() const;
    void set_M22(float m11);

    /// <summary>Gets or sets the value of the third row and first column of this <see cref="T:System.Windows.Media.Matrix" /> structure.  </summary>
    /// <returns>The value of the third row and first column of this <see cref="T:System.Windows.Media.Matrix" /> structure. The default value is 0.</returns>
    float get_OffsetX() const;
    void set_OffsetX(float offsetX);

    /// <summary>Gets or sets the value of the third row and second column of this <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
    /// <returns>The value of the third row and second column of this <see cref="T:System.Windows.Media.Matrix" /> structure. The default value is 0.</returns>
    float get_OffsetY() const;
    void set_OffsetY(float offsetY);

#pragma endregion Public Properties

public:

#pragma region Internal Methods

    static Matrix CreateRotationRadians(float angle);
    static Matrix CreateRotationRadians(float angle, float centerX, float centerY);
    static Matrix CreateScaling(float scaleX, float scaleY);
    static Matrix CreateScaling(float scaleX, float scaleY, float centerX, float centerY);
    static Matrix CreateSkewRadians(float skewX, float skewY);
    static Matrix CreateTranslation(float offsetX, float offsetY);
    void MultiplyPoint(float* pX, float* pY) const;
    void MultiplyVector(float* pX, float* pY) const;

#pragma endregion Internal Methods

private:

#pragma region Private Methods

    static Matrix CreateIdentity();
    void DeriveMatrixType();
    void SetMatrix(float m11, float m12, float m21, float m22, float offsetX, float offsetY, MatrixTypes::Enum type);

#pragma endregion Private Methods

#pragma region Private Properties

    bool get_IsDistinguishedIdentity() const;

#pragma endregion Private Properties

#pragma region Private Fields

    float m_m11;
    float m_m12;
    float m_m21;
    float m_m22;
    float m_offsetX;
    float m_offsetY;
    int m_padding;
    unsigned int m_type;

#pragma endregion Private Fields

};

/// <summary> Multiplies a <see cref="T:System.Windows.Media.Matrix" /> structure by another <see cref="T:System.Windows.Media.Matrix" /> structure. </summary>
/// <returns>The result of multiplying <paramref name="lhs" /> by <paramref name="rhs" />.</returns>
/// <param name="lhs">The first <see cref="T:System.Windows.Media.Matrix" /> structure to multiply.</param>
/// <param name="rhs">The second <see cref="T:System.Windows.Media.Matrix" /> structure to multiply.</param>
Matrix operator*(const Matrix& lhs, const Matrix& rhs);


}; // namespace AVUI
